package com.nulldev.util.web.flexibleKit.scfk.io.streams;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.SocketChannel;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.atomic.AtomicReference;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.util.random.GlobalRandom;
import com.nulldev.util.data.Variables;
import com.nulldev.util.networking.misc.TLSUtil;
import com.nulldev.util.networking.misc.tls.TLSStandardFiltering;
import com.nulldev.util.web.HttpServer3.libSocketReader.tlschannel.ClientTlsChannel;
import com.nulldev.util.web.flexibleKit.scfk.SCFKRequest;
import com.nulldev.util.web.flexibleKit.scfk.utils.TLSCipherPolicy;

public class StreamGenerator {

	private static final AtomicReference<SSLContext> context_standard = new AtomicReference<SSLContext>();
	private static final AtomicReference<SSLContext> context_modern = new AtomicReference<SSLContext>();

	private static SSLContext getStandardContext() {
		if (context_standard.get() == null) {
			try {
				final SSLContext sst = SSLContext.getInstance(TLSUtil.getTLSForSSL());
				JVM.println("standard: " + sst.getProvider());
				final TrustManager[] trustAllCerts = new TrustManager[]
					{ new X509TrustManager() {
						@Override
						public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
						}

						@Override
						public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
						}

						@Override
						public java.security.cert.X509Certificate[] getAcceptedIssuers() {
							return null;
						}
					} };
				sst.init(null, trustAllCerts, GlobalRandom.getGlobalInsecureRandom());
				context_standard.set(sst);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
			return context_standard.get();
		} else
			return context_standard.get();
	}

	private static SSLContext getModernContext() {
		if (context_modern.get() == null) {
			try {
				final SSLContext sst = SSLContext.getInstance(TLSUtil.getTLSVersion());
				JVM.println("modern: " + sst.getProvider());
				final TrustManager[] trustAllCerts = new TrustManager[]
					{ new X509TrustManager() {
						@Override
						public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
						}

						@Override
						public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
						}

						@Override
						public java.security.cert.X509Certificate[] getAcceptedIssuers() {
							return null;
						}
					} };
				sst.init(null, trustAllCerts, GlobalRandom.getGlobalInsecureRandom());
				context_modern.set(sst);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
			return context_modern.get();
		} else
			return context_modern.get();
	}

	private static final String[] PROTOCOLS_HTTP2 = new String[]
		{ "h2", "http/1.1" };
	private static final String[] PROTOCOLS_HTTP1 = new String[]
		{ "http/1.1" };

	public static Stream openStream(final SCFKRequest r) throws IOException, NoSuchAlgorithmException {
		return openStream(r, defaultPolicy);
	}

	private static final TLSCipherPolicy defaultPolicy = TLSCipherPolicy.ENABLE_NULLUTIL_CIPHERS;

	@SuppressWarnings("resource")
	public static Stream openStream(final SCFKRequest r, final TLSCipherPolicy policy) throws IOException, NoSuchAlgorithmException {
		Variables.notNullE(r);
		Variables.notNullE(r.getURL());
		Variables.notNullE(r.tosPolicy());
		final boolean isHTTPS = r.getURL().getScheme().equalsIgnoreCase("https");
		final SocketChannel sc = SocketChannel.open();
		sc.configureBlocking(true); // FIXME: I break SSL if non-blocking (and the Channels newInputStream impl)
		sc.connect(new InetSocketAddress(r.getURL().getHostname(), r.getURL().getPort()));
		sc.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
		sc.setOption(StandardSocketOptions.SO_REUSEADDR, true);
		sc.setOption(StandardSocketOptions.IP_TOS, r.tosPolicy().val());
		if (isHTTPS) {
			final ClientTlsChannel ctls;
			switch (r.getProtocol()) {
				case HTTP_2_0: {
					final SSLEngine ssle = getModernContext().createSSLEngine();
					ssle.setUseClientMode(true);
					ssle.getSSLParameters().setProtocols(PROTOCOLS_HTTP2);
					switch (policy) {
						case ENABLE_ALL_CIPHERS: {
							ssle.setEnabledCipherSuites(ssle.getSupportedCipherSuites());
							break;
						}
						case ENABLE_NULLUTIL_CIPHERS: {
							ssle.setEnabledCipherSuites(TLSStandardFiltering.filterCiphers(ssle.getSupportedCipherSuites()));
							break;
						}
						case REQUIRE_SECURE_CIPHERS: {
							ssle.setEnabledCipherSuites(TLSCipherPolicy.REQUIRE_SECURE_CIPHERS_CIPHERS);
							break;
						}
						default:
							break;
					}
					ctls = ClientTlsChannel.newBuilder(sc, ssle).build();
					break;
				}
				default: {
					final SSLEngine ssle = getStandardContext().createSSLEngine();
					ssle.setUseClientMode(true);
					ssle.getSSLParameters().setProtocols(PROTOCOLS_HTTP1);
					switch (policy) {
						case ENABLE_ALL_CIPHERS: {
							ssle.setEnabledCipherSuites(ssle.getSupportedCipherSuites());
							break;
						}
						case ENABLE_NULLUTIL_CIPHERS: {
							ssle.setEnabledCipherSuites(TLSStandardFiltering.filterCiphers(ssle.getSupportedCipherSuites()));
							break;
						}
						case REQUIRE_SECURE_CIPHERS: {
							ssle.setEnabledCipherSuites(TLSCipherPolicy.REQUIRE_SECURE_CIPHERS_CIPHERS);
							break;
						}
						default:
							break;
					}
					ctls = ClientTlsChannel.newBuilder(sc, ssle).build();
					break;
				}
			}
			final Stream s = new Stream(sc, ctls).use();
			StreamManager.tryCache(r.getURL().getHost(), s);
			return s;
		} else {
			final Stream s = new Stream(sc).use();
			StreamManager.tryCache(r.getURL().getHost(), s);
			return s;
		}
	}

	public static Stream autoStream(final SCFKRequest r) throws IOException, NoSuchAlgorithmException {
		return autoStream(r, defaultPolicy);
	}

	public static Stream autoStream(final SCFKRequest r, final TLSCipherPolicy policy) throws IOException, NoSuchAlgorithmException {
		Variables.notNullE(r);
		Variables.notNullE(r.getURL());
		final Stream ls = StreamManager.tryFetch(r.getURL().getHost());
//		JVM.println("Requesting stream for: " + r.getURL());
//		JVM.println("stream[ls]: " + ls);
//		JVM.println("streams:");
//		ArrayUtils.printLln(StreamManager.streams());
		if (ls == null || ls.isClosed() || ls.isInUse())
			return openStream(r, policy);
		else
			return ls;
	}
}
